Honor UnmappedMemberHandling in AIFunctionFactory parameter binding#7474
Conversation
When JsonSerializerOptions.UnmappedMemberHandling is set to Disallow, AIFunction invocations will now throw ArgumentException if the provided AIFunctionArguments contains keys that do not correspond to any declared parameter of the underlying method. This mirrors STJ's handling of unmapped properties for object deserialization and enables opt-in strict validation of tool call arguments. Addresses discussion in modelcontextprotocol/csharp-sdk#1508. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates AIFunctionFactory’s top-level argument binding to optionally enforce strict validation of AIFunctionArguments keys when JsonSerializerOptions.UnmappedMemberHandling is set to Disallow, aligning dictionary binding behavior with System.Text.Json’s unmapped-member handling semantics.
Changes:
- Enforce strict “no extra keys” validation during
AIFunction.InvokeAsyncwhenUnmappedMemberHandling=Disallow. - Document the new strict-validation behavior on
AIFunctionFactoryOptions.SerializerOptions. - Add unit tests covering both strict (
Disallow) and default (Skip) behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs | Adds tests validating strict vs default handling of extra argument keys. |
| src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactoryOptions.cs | Documents how UnmappedMemberHandling=Disallow affects invocation-time argument validation. |
| src/Libraries/Microsoft.Extensions.AI.Abstractions/Functions/AIFunctionFactory.cs | Implements invocation-time unmapped argument key validation and precomputes expected argument names. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Always build the expected-argument-name set (even if empty) so strict mode also flags extra keys on parameterless methods / methods with only infrastructure parameters. - Skip strict unmapped-key validation when any parameter uses a custom ParameterBindingOptions.BindParameter callback, since those may legitimately consume arbitrary argument keys. - Expanded the XML docs on AIFunctionFactoryOptions.SerializerOptions to clarify which parameter names are considered valid and that custom parameter binding bypasses the strict check. - Added tests for both new behaviors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
FYI @jeffhandley we are thinking of merging this PR which technically speaking constitutes a breaking chagne. Before this PR, using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.AI;
// Shared strict options — common in codebases that want strict
// JSON deserialization across the board.
var strictOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
};
// Create an AIFunction with those options.
AIFunction func = AIFunctionFactory.Create(
(string taskId, string update) => $"{taskId}: {update}",
new AIFunctionFactoryOptions { SerializerOptions = strictOptions });
// An LLM returns an extra hallucinated argument ("phase").
// BEFORE this PR → silently ignored, call succeeds.
// AFTER this PR → throws ArgumentException:
// "The arguments dictionary contains an unexpected key 'phase'
// that does not correspond to any parameter of '...'"
var result = await func.InvokeAsync(new AIFunctionArguments
{
["taskId"] = "T-42",
["update"] = "Finished",
["phase"] = "completed", // ← not a real parameter
}); |
Follow-up to the discussion in modelcontextprotocol/csharp-sdk#1508.
Today,
AIFunctionFactorysilently ignores keys in theAIFunctionArgumentsdictionary that do not correspond to any declared parameter of the underlying method. This matches STJ's default lenient handling of unmapped properties, but it prevents tool servers from opting in to strict validation when an LLM hallucinates an extra argument name (e.g.markCompletevsphasein the linked issue).This change wires
JsonSerializerOptions.UnmappedMemberHandlinginto top-level parameter binding:UnmappedMemberHandling = Disallow, invoking theAIFunctionnow throwsArgumentExceptionifAIFunctionArgumentscontains a key that doesn't match any method parameter (infrastructure parametersCancellationToken/AIFunctionArguments/IServiceProviderare excluded from the permitted set, as they are never addressed by name).Skip) is unchanged, so this is opt-in and non-breaking.AIFunctionFactoryOptions.SerializerOptions.Closes the MEAI side of modelcontextprotocol/csharp-sdk#1508.
Microsoft Reviewers: Open in CodeFlow